package cn.dansj.common.request.configure;

import cn.dansj.common.processor.TargetType;
import cn.dansj.common.processor.meta.WriteToResource;
import cn.dansj.common.request.annotation.permission.Permission;
import cn.dansj.common.utils.enums.BooleanType;
import cn.dansj.common.utils.enums.UserType;
import cn.dansj.common.utils.spring.AnnotationUtils;
import cn.dansj.common.utils.spring.context.ApplicationContextProvider;
import cn.dansj.common.utils.spring.request.RequestUtils;
import cn.dansj.common.utils.transfer.Transformation;

import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;

import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.Order;
import org.springframework.http.server.PathContainer;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.HandlerExecutionChain;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
import org.springframework.web.util.pattern.PathPatternParser;

import java.io.IOException;
import java.lang.reflect.Method;

/**
 * 拦截请求，检验权限
 */
@WriteToResource(type = TargetType.Imports, keyProperty = "org.springframework.boot.autoconfigure.AutoConfiguration")
@Order(1)
public class PermissionFilterConfiguration implements Filter {
    private final RequestMappingHandlerMapping requestMappingHandlerMapping;
    private final PermissionFilter permissionFilter;
    private static final PathPatternParser parser = new PathPatternParser();

    public PermissionFilterConfiguration(WebApplicationContext webApplicationContext) {
        this.requestMappingHandlerMapping = webApplicationContext.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
        this.permissionFilter = ApplicationContextProvider.hasBean(PermissionFilter.class) ? ApplicationContextProvider.getBean(PermissionFilter.class) : DefaultBeanConfiguration.permissionFilter();
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws ServletException, IOException {
        if (servletRequest instanceof HttpServletRequest) {
            HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
            final boolean notExclude = permissionFilter.excludePathPatterns().stream().noneMatch(e -> parser.parse(e).matches(PathContainer.parsePath(httpServletRequest.getRequestURI())));
            final boolean ignoreWebsocketRequest = permissionFilter.ignoreWebsocketRequest() && "websocket".equalsIgnoreCase(httpServletRequest.getHeader("Upgrade"));
            if (!ignoreWebsocketRequest && notExclude && !checkAuth(httpServletRequest)) return;
        }
        filterChain.doFilter(servletRequest, servletResponse);
        permissionFilter.afterCompletion(servletRequest, servletResponse);
    }

    private boolean checkAuth(HttpServletRequest httpServletRequest) {
        try {
            final HandlerExecutionChain handlerExecutionChain = requestMappingHandlerMapping.getHandler(httpServletRequest);
            final Method method = handlerExecutionChain != null && handlerExecutionChain.getHandler() instanceof HandlerMethod ? ((HandlerMethod) handlerExecutionChain.getHandler()).getMethod() : null;
            return permissionFilter.checkAuth(httpServletRequest, method, getPermission(method), checkURINotExist(httpServletRequest.getRequestURI(), httpServletRequest.getMethod()));
        } catch (Exception exception) {
            return Transformation.castToBoolean(RequestUtils.response404());
        }
    }

    private Permission getPermission(Method method) {
        // 获取方法的注解
        Permission methodPermission = method == null ? null : AnnotatedElementUtils.findMergedAnnotation(method, Permission.class);
        // 获取类上的注解
        Permission classPermission = method == null ? null : AnnotatedElementUtils.findMergedAnnotation(method.getDeclaringClass(), Permission.class);
        // 判断方法注解是否生效
        final Permission fromMethod = methodPermission == null || methodPermission.value() == null || methodPermission.value().equals(BooleanType.UNSET) ? null : methodPermission;
        // 判断类注解是否生效
        final Permission fromClass = classPermission == null || classPermission.value() == null || classPermission.value().equals(BooleanType.UNSET) ? null : classPermission;

        // 确定最终的权限标识
        BooleanType booleanType = fromMethod == null ? fromClass == null ? BooleanType.UNSET : fromClass.value() : fromMethod.value();
        // 确定用户标识, method上的为空或者继承则用class的, 否则返回method自己的, class的为空或者继承则返回None, 否则返回类的用户标识
        UserType userType = methodPermission == null || methodPermission.userType().equals(UserType.INHERITED) ?
                classPermission == null || classPermission.userType().equals(UserType.INHERITED) ? UserType.NONE : classPermission.userType() : methodPermission.userType();
        return AnnotationUtils.newInstance(Permission.class, "value", booleanType, "userType", userType);
    }

    public static RequestMethod resolve(String method) {
        Assert.notNull(method, "Method must not be null");
        switch (method) {
            case "GET":
                return RequestMethod.GET;
            case "HEAD":
                return RequestMethod.HEAD;
            case "POST":
                return RequestMethod.POST;
            case "PUT":
                return RequestMethod.PUT;
            case "PATCH":
                return RequestMethod.PATCH;
            case "DELETE":
                return RequestMethod.DELETE;
            case "OPTIONS":
                return RequestMethod.OPTIONS;
            case "TRACE":
                return RequestMethod.TRACE;
            default:
                return null;
        }
    }

    /**
     * 判断当前路由是否在地址池中
     *
     * @param uri    当前地址
     * @param method get、post
     */
    private boolean checkURINotExist(String uri, String method) {
        for (Endpoints.RequestMapping mapping : Endpoints.requestMappingList) {
            if (parser.parse(mapping.getUrlPattern()).matches(PathContainer.parsePath(uri)) && (method == null || mapping.getMethod().equals(resolve(method)))) {
                return false;
            }
        }
        return true;
    }
}
